import { expect, test } from "@odoo/hoot";
import {
    clear,
    click,
    edit,
    hover,
    keyDown,
    pointerDown,
    press,
    queryAll,
    queryAllTexts,
    queryFirst,
    runAllTimers,
} from "@odoo/hoot-dom";
import { Deferred, animationFrame, mockTimeZone, mockTouch } from "@odoo/hoot-mock";
import { Component, onWillUpdateProps, xml } from "@odoo/owl";
import {
    SELECTORS,
    addNewRule,
    clickOnButtonDeleteNode,
    editValue,
    getCurrentOperator,
    getCurrentPath,
    getCurrentValue,
    label,
} from "@web/../tests/core/tree_editor/condition_tree_editor_test_helpers";
import {
    contains,
    defineActions,
    defineModels,
    defineWebModels,
    editSearch,
    fields,
    getFacetTexts,
    getService,
    models,
    mountWebClient,
    mountWithCleanup,
    mountWithSearch,
    onRpc,
    removeFacet,
    selectGroup,
    serverState,
    toggleMenuItem,
    toggleSearchBarMenu,
    validateSearch,
} from "@web/../tests/web_test_helpers";
import { cookie } from "@web/core/browser/cookie";
import { SearchBar } from "@web/search/search_bar/search_bar";
import { useSearchBarToggler } from "@web/search/search_bar/search_bar_toggler";
class Partner extends models.Model {
    name = fields.Char();
    bar = fields.Many2one({ relation: "partner" });
    birthday = fields.Date();
    birth_datetime = fields.Datetime({ string: "Birth DateTime" });
    foo = fields.Char();
    bool = fields.Boolean();
    company = fields.Many2one({ relation: "partner" });
    properties = fields.Properties({
        definition_record: "bar",
        definition_record_field: "child_properties",
    });
    child_properties = fields.PropertiesDefinition();

    _records = [
        {
            id: 1,
            name: "First record",
            foo: "yop",
            bar: 2,
            bool: true,
            birthday: "1983-07-15",
            birth_datetime: "1983-07-15 01:00:00",
        },
        {
            id: 2,
            name: "Second record",
            foo: "blip",
            bar: 1,
            bool: false,
            birthday: "1982-06-04",
            birth_datetime: "1982-06-04 02:00:00",
            company: 1,
        },
        {
            id: 3,
            name: "Third record",
            foo: "gnap",
            bar: 1,
            bool: false,
            birthday: "1985-09-13",
            birth_datetime: "1985-09-13 03:00:00",
            company: 5,
        },
        {
            id: 4,
            name: "Fourth record",
            foo: "plop",
            bar: 2,
            bool: true,
            birthday: "1983-05-05",
            birth_datetime: "1983-05-05 04:00:00",
        },
        {
            id: 5,
            name: "Fifth record",
            foo: "zoup",
            bar: 2,
            bool: true,
            birthday: "1800-01-01",
            birth_datetime: "1800-01-01 05:00:00",
        },
    ];
    _views = {
        list: `<list><field name="foo"/></list>`,
        search: `
            <search>
                <field name="foo"/>
                <field name="birthday"/>
                <field name="birth_datetime"/>
                <field name="bar" context="{'bar': self}"/>
                <field name="company" domain="[('bool', '=', True)]"/>
                <filter string="Birthday" name="date_filter" date="birthday"/>
                <filter string="Birthday" name="date_group_by" context="{'group_by': 'birthday:day'}"/>
            </search>
        `,
        form: `
            <form>
                <field name="foo" />
                <field name="bool" />
            </form>
        `,
    };
}

defineModels([Partner]);

defineActions([
    {
        id: 1,
        name: "Partners Action",
        res_model: "partner",
        search_view_id: [false, "search"],
        views: [
            [false, "list"],
            [false, "form"],
        ],
    },
]);

test.tags("desktop");
test("basic rendering", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });
    expect(queryFirst`.o_searchview input`).toBeFocused();
});

test.tags("desktop");
test("navigation with facets", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: ["groupBy"],
        searchViewId: false,
        context: { search_default_date_group_by: 1 },
    });

    expect(`.o_searchview .o_searchview_facet`).toHaveCount(1);
    expect(queryFirst`.o_searchview input`).toBeFocused();

    await keyDown("ArrowLeft"); // press left to focus the facet
    await animationFrame();
    expect(queryFirst`.o_searchview .o_searchview_facet`).toBeFocused();

    await keyDown("ArrowRight"); // press right to focus the input
    await animationFrame();
    expect(queryFirst`.o_searchview input`).toBeFocused();
});

test.tags("desktop");
test("navigation with facets (2)", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: ["groupBy"],
        searchViewId: false,
        context: {
            search_default_date_group_by: 1,
            search_default_foo: 1,
        },
    });

    expect(`.o_searchview .o_searchview_facet`).toHaveCount(2);
    expect(queryFirst`.o_searchview input`).toBeFocused();

    // press left to focus the rightmost facet
    await keyDown("ArrowLeft");
    await animationFrame();
    expect(queryFirst`.o_searchview .o_searchview_facet:nth-child(2)`).toBeFocused();

    // press left to focus the leftmost facet
    await keyDown("ArrowLeft");
    await animationFrame();
    expect(queryFirst`.o_searchview .o_searchview_facet:nth-child(1)`).toBeFocused();

    // press left to focus the input
    await keyDown("ArrowLeft");
    await animationFrame();
    expect(queryFirst`.o_searchview input`).toBeFocused();

    // press left to focus the leftmost facet
    await keyDown("ArrowRight");
    await animationFrame();
    expect(queryFirst`.o_searchview .o_searchview_facet:nth-child(1)`).toBeFocused();
});

test.tags("mobile");
test("search input is focused when being toggled", async () => {
    class Parent extends Component {
        static template = xml`
            <div>
                <t t-component="searchBarToggler.component" t-props="searchBarToggler.props"/>
                <SearchBar toggler="searchBarToggler"/>
            </div>
        `;
        static components = { SearchBar };
        static props = ["*"];
        setup() {
            this.searchBarToggler = useSearchBarToggler();
        }
    }
    await mountWithSearch(Parent, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });
    expect(".o_searchview input").toHaveCount(0);
    await contains(`button .fa-search`).click();
    expect(".o_searchview input").toHaveCount(1);
    expect(queryFirst`.o_searchview input`).toBeFocused();
});

test.tags("desktop");
test("search input is not focused on larger touch devices", async () => {
    mockTouch(true);
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });
    expect(".o_searchview input").toHaveCount(1);
    expect(".o_searchview input").not.toBeFocused();
});

test("search date and datetime fields. Support of timezones", async () => {
    mockTimeZone(6);

    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });

    // Date case
    await editSearch("07/15/1983");
    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("Enter");
    await animationFrame();
    expect(getFacetTexts().map((str) => str.replace(/\s+/g, " "))).toEqual(["Birthday 07/15/1983"]);
    expect(searchBar.env.searchModel.domain).toEqual([["birthday", "=", "1983-07-15"]]);

    // Close Facet
    await click(`.o_searchview_facet .o_facet_remove`);
    await animationFrame();

    // DateTime case
    await editSearch("07/15/1983 00:00:00");
    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("Enter");
    await animationFrame();
    expect(getFacetTexts().map((str) => str.replace(/\s+/g, " "))).toEqual([
        "Birth DateTime 07/15/1983 00:00:00",
    ]);
    expect(searchBar.env.searchModel.domain).toEqual([
        ["birth_datetime", "=", "1983-07-14 18:00:00"],
    ]);
});

test("autocomplete menu clickout interactions", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: /* xml */ `
            <search>
                <field name="bar"/>
                <field name="birthday"/>
                <field name="birth_datetime"/>
                <field name="foo"/>
                <field name="bool"/>
            </search>
        `,
    });

    // Create an input outside of the search panel to simulate another input outside of the search panel
    await mountWithCleanup(/* xml */ `<input id="foo"/>`);

    expect(`.o_searchview_autocomplete`).toHaveCount(0);

    await editSearch("Hello there");
    expect(`.o_searchview input`).toHaveValue("Hello there");
    expect(`.o_searchview_autocomplete`).toHaveCount(1);

    await keyDown("Escape");
    await animationFrame();
    expect(`.o_searchview input`).toHaveValue("");
    expect(`.o_searchview_autocomplete`).toHaveCount(0);

    await editSearch("General Kenobi");
    expect(`.o_searchview input`).toHaveValue("General Kenobi");
    expect(`.o_searchview_autocomplete`).toHaveCount(1);

    await click(`input#foo`);
    await animationFrame();
    await runAllTimers();
    expect(`.o_searchview input`).toHaveValue("");
    expect(`.o_searchview_autocomplete`).toHaveCount(0);
    expect("input#foo").toBeFocused();
});

test("select an autocomplete field", async () => {
    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });

    await editSearch("a");
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(4);

    await keyDown("Enter");
    await animationFrame();
    expect(`.o_searchview_input_container .o_facet_values`).toHaveText("a");
    expect(searchBar.env.searchModel.domain).toEqual([["foo", "ilike", "a"]]);
});

test("select an autocomplete field with `context` key", async () => {
    let updateCount = 0;
    class TestComponent extends Component {
        static template = xml`<SearchBar/>`;
        static components = { SearchBar };
        static props = ["*"];
        setup() {
            onWillUpdateProps(() => {
                updateCount++;
            });
        }
    }

    const searchBar = await mountWithSearch(TestComponent, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });

    // 'r' key to filter on bar "First Record"
    await editSearch("record");
    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("ArrowRight");
    await animationFrame();
    await runAllTimers();
    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("Enter");
    await animationFrame();
    expect(getFacetTexts().map((str) => str.replace(/\s+/g, " "))).toEqual(["Bar First record"]);
    expect(updateCount).toBe(1);
    expect(searchBar.env.searchModel.domain).toEqual([["bar", "=", 1]]);
    expect(searchBar.env.searchModel.context.bar).toEqual([1]);

    // 'r' key to filter on bar "Second Record"
    await editSearch("record");
    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("ArrowRight");
    await animationFrame();
    await runAllTimers();
    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("Enter");
    await animationFrame();
    expect(getFacetTexts().map((str) => str.replace(/\s+/g, " "))).toEqual([
        "Bar First record or Second record",
    ]);
    expect(updateCount).toBe(2);
    expect(searchBar.env.searchModel.domain).toEqual(["|", ["bar", "=", 1], ["bar", "=", 2]]);
    expect(searchBar.env.searchModel.context.bar).toEqual([1, 2]);
});

test.tags("desktop");
test("no search text triggers a reload", async () => {
    let updateCount = 0;
    class TestComponent extends Component {
        static template = xml`<SearchBar/>`;
        static components = { SearchBar };
        static props = ["*"];
        setup() {
            onWillUpdateProps(() => {
                updateCount++;
            });
        }
    }

    await mountWithSearch(TestComponent, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });
    await keyDown("Enter");
    await animationFrame();
    expect(`.o_searchview_facet_label`).toHaveCount(0);
    expect(updateCount).toBe(1);
});

test("selecting (no result) triggers a search bar rendering", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="bar"/>
            </search>
        `,
    });

    await editSearch("hello there");

    // 'a' key to filter nothing on bar
    await keyDown("ArrowRight");
    await animationFrame();
    await runAllTimers();
    await keyDown("ArrowDown");
    await animationFrame();
    expect(`.o_searchview_autocomplete .focus`).toHaveText("(no result)");

    await keyDown("Enter");
    await animationFrame();
    expect(`.o_searchview_facet_label`).toHaveCount(0);
    expect(`.o_searchview input`).toHaveValue("");
});

test("update suggested filters in autocomplete menu with Japanese IME", async () => {
    // The goal here is to simulate as many events happening during an IME
    // assisted composition session as possible. Some of these events are
    // not handled but are triggered to ensure they do not interfere.
    const TEST = "TEST";
    const TEST_JP = "テスト";

    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });

    await click(".o_searchview input");

    // Simulate typing "TEST" on search view.
    await edit(TEST, { composition: true });
    await animationFrame();
    expect(`.o_searchview_autocomplete`).toHaveCount(1);
    expect(`.o_searchview_autocomplete .o-dropdown-item:first`).toHaveText(
        `Search Foo for: ${TEST}`
    );

    // Simulate soft-selection of another suggestion from IME through keyboard navigation.
    await edit(TEST_JP, { composition: true });
    await animationFrame();
    expect(`.o_searchview_autocomplete .o-dropdown-item:first`).toHaveText(
        `Search Foo for: ${TEST_JP}`
    );

    // Simulate selection on suggestion item "TEST" from IME.
    await edit(TEST, { composition: true });
    await animationFrame();
    expect(`.o_searchview_autocomplete`).toHaveCount(1);
    expect(queryFirst`.o_searchview_autocomplete .o-dropdown-item`).toHaveText(
        `Search Foo for: ${TEST}`
    );
});

test("open search view autocomplete on paste value using mouse", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });

    // Simulate paste text through the mouse.
    await navigator.clipboard.writeText("ABC");
    await pointerDown(".o_searchview input");
    await press(["ctrl", "v"]);
    await animationFrame();
    expect(`.o_searchview_autocomplete`).toHaveCount(1);
});

test("select autocompleted many2one", async () => {
    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="foo"/>
                <field name="birthday"/>
                <field name="birth_datetime"/>
                <field name="bar" operator="child_of"/>
            </search>
        `,
    });
    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("rec");
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-last-child(2)").click();
    expect(searchBar.env.searchModel.domain).toEqual([["bar", "child_of", "rec"]]);

    await removeFacet("Bar rec");
    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("rec");
    await contains(".o_expand").click();
    await contains(".o_searchview_autocomplete .o-dropdown-item.o_indent").click();
    expect(searchBar.env.searchModel.domain).toEqual([["bar", "child_of", 1]]);
});

test(`"null" as autocomplete value`, async () => {
    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });
    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("null");
    expect(`.o_searchview_autocomplete .focus`).toHaveText("Search Foo for: null");

    await contains(".o_searchview_autocomplete .o-dropdown-item.focus a").click();
    expect(searchBar.env.searchModel.domain).toEqual([["foo", "ilike", "null"]]);
});

test("autocompletion with a boolean field", async () => {
    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="bool"/>
            </search>
        `,
    });
    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("y");
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(2);
    expect(`.o_searchview_autocomplete .o-dropdown-item:nth-last-child(2)`).toHaveText(
        "Search Bool for: Yes"
    );

    // select "Yes"
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-last-child(2)").click();
    expect(searchBar.env.searchModel.domain).toEqual([["bool", "=", true]]);

    await removeFacet("Bool Yes");
    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("No");
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(2);
    expect(`.o_searchview_autocomplete .o-dropdown-item:nth-last-child(2)`).toHaveText(
        "Search Bool for: No"
    );

    // select "No"
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-last-child(2)").click();
    expect(searchBar.env.searchModel.domain).toEqual([["bool", "=", false]]);
});

test("autocompletion with a selection field", async () => {
    Partner._fields.selection_field = fields.Selection({
        string: "Selection Field",
        selection: [
            ["abc", "ABC"],
            ["aef", "AEF"],
            ["ghi", "GHI"],
        ],
    });
    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="selection_field"/>
            </search>
        `,
    });
    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("a");
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(2);
    expect(`.o_searchview_autocomplete .o-dropdown-item:first`).toHaveText(
        "Search Selection Field for: a"
    );
    // expand results
    await contains(".o_searchview_autocomplete .o-dropdown-item:first").click();
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(4);
    expect(`.o_searchview_autocomplete .o-dropdown-item:eq(1)`).toHaveText("ABC");
    expect(`.o_searchview_autocomplete .o-dropdown-item:eq(2)`).toHaveText("AEF");
    // select "AEF"
    await contains(`.o_searchview_autocomplete .o-dropdown-item:eq(2)`).click();
    expect(searchBar.env.searchModel.domain).toEqual([["selection_field", "=", "aef"]]);

    await removeFacet("Selection Field AEF");
    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("h");
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(2);
    expect(`.o_searchview_autocomplete .o-dropdown-item:first`).toHaveText(
        "Search Selection Field for: h"
    );
    // expand results
    await contains(".o_searchview_autocomplete .o-dropdown-item:first").click();
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(3);
    expect(`.o_searchview_autocomplete .o-dropdown-item:eq(1)`).toHaveText("GHI");

    // select "GHI"
    await contains(`.o_searchview_autocomplete .o-dropdown-item:eq(1)`).click();
    expect(searchBar.env.searchModel.domain).toEqual([["selection_field", "=", "ghi"]]);
});

test("the search value is trimmed to remove unnecessary spaces", async () => {
    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="foo" filter_domain="[('foo', 'ilike', self)]"/>
            </search>
        `,
    });
    await editSearch("bar");
    await validateSearch();
    expect(searchBar.env.searchModel.domain).toEqual([["foo", "ilike", "bar"]]);

    await removeFacet("Foo bar");
    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("   bar ");
    await validateSearch();
    expect(searchBar.env.searchModel.domain).toEqual([["foo", "ilike", "bar"]]);
});

test("reference fields are supported in search view", async () => {
    Partner._fields.ref = fields.Reference({ selection: [["partner", "Partner"]] });

    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="ref"/>
            </search>
        `,
    });
    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("ref");
    await validateSearch();
    expect(searchBar.env.searchModel.domain).toEqual([["ref", "ilike", "ref"]]);

    await removeFacet("Ref ref");
    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("ref002");
    await validateSearch();
    expect(searchBar.env.searchModel.domain).toEqual([["ref", "ilike", "ref002"]]);
});

test("expand an asynchronous menu and change the selected item with the mouse during expansion", async () => {
    const def = new Deferred();
    onRpc("name_search", () => def);
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="bar" operator="child_of"/>
            </search>
        `,
    });
    await editSearch("rec");
    await contains(`.o_expand`).click();
    await contains(`.o_searchview_autocomplete .o-dropdown-item:first-child`).hover();
    expect(`.o_searchview_autocomplete .o-dropdown-item.o_indent`).toHaveCount(0);

    def.resolve();
    await animationFrame();
    expect(`.o_searchview_autocomplete .o-dropdown-item.o_indent`).toHaveCount(5);
});

test("expand an asynchronous menu and change the selected item with the arrow during expansion", async () => {
    const def = new Deferred();
    onRpc("name_search", () => def);
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="bar" operator="child_of"/>
            </search>
        `,
    });
    await editSearch("rec");
    await contains(".o_expand").click();
    await keyDown("ArrowDown");
    await animationFrame();
    expect(`.o_searchview_autocomplete .o-dropdown-item.o_indent`).toHaveCount(0);

    def.resolve();
    await animationFrame();
    expect(`.o_searchview_autocomplete .o-dropdown-item.o_indent`).toHaveCount(5);
});

test("checks that an arrowDown always selects an item", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="bar" operator="child_of"/>
            </search>
        `,
    });
    await editSearch("rec");
    await contains(".o_expand").click();
    await hover(`.o_searchview_autocomplete .o-dropdown-item.o_indent:last`);
    await contains(".o_expand").click();
    await keyDown("ArrowDown");
    expect(".o_searchview_autocomplete .focus").toHaveCount(1);
});

test("checks that an arrowUp always selects an item", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="bar" operator="child_of"/>
            </search>
        `,
    });
    await editSearch("rec");
    await contains(".o_expand").click();
    await hover(`.o_searchview_autocomplete .o-dropdown-item.o_indent:last`);
    await contains(".o_expand").click();
    await keyDown("ArrowUp");
    expect(".o_searchview_autocomplete .focus").toHaveCount(1);
});

test("many2one_reference fields are supported in search view", async () => {
    Partner._fields.res_id = fields.Many2oneReference({
        string: "Resource ID",
        model_field: "bar",
        relation: "partner",
    });

    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: /*xml*/ `
            <search>
                <field name="foo" />
                <field name="res_id" />
            </search>
        `,
    });

    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("12");
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Foo for: 12",
        "Search Resource ID for: 12",
        "Custom Filter...",
    ]);

    await keyDown("ArrowDown");
    await validateSearch();
    expect(searchBar.env.searchModel.domain).toEqual([["res_id", "=", 12]]);

    await removeFacet("Resource ID 12");
    expect(searchBar.env.searchModel.domain).toEqual([]);

    await editSearch("1a");
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Foo for: 1a",
        "Custom Filter...",
    ]);

    await validateSearch();
    expect(searchBar.env.searchModel.domain).toEqual([["foo", "ilike", "1a"]]);
});

test("check kwargs of a rpc call with a domain", async () => {
    onRpc("name_search", (params) => {
        expect(params).toMatchObject({
            model: "partner",
            method: "name_search",
            args: [],
            kwargs: {
                domain: [["bool", "=", true]],
                limit: 8 + 1,
                name: "F",
            },
        });
    });

    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });

    await editSearch("F");
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(4);

    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("ArrowRight");
    await animationFrame();
    await runAllTimers();
    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("ArrowDown");
    await animationFrame();
    await keyDown("Enter");
    await animationFrame();
    expect(searchBar.env.searchModel.domain).toEqual([["company", "=", 5]]);
});

test("should wait label promises for many2one search defaults", async () => {
    const def = new Deferred();
    onRpc("read", () => def);

    mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        context: { search_default_company: 1 },
    });
    await animationFrame();
    expect(`.o_cp_searchview`).toHaveCount(0);

    def.resolve();
    await animationFrame();
    expect(`.o_cp_searchview`).toHaveCount(1);
    expect(getFacetTexts()[0].replace("\n", "")).toBe("CompanyFirst record");
});

test("should wait label promises for many2many search defaults", async () => {
    Partner._fields.m2m = fields.Many2many({ relation: "partner" });
    const def = new Deferred();
    onRpc("read", () => def);

    mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="m2m"/>
            </search>
        `,
        context: { search_default_m2m: [1, 2] },
    });
    await animationFrame();
    expect(`.o_cp_searchview`).toHaveCount(0);

    def.resolve();
    await animationFrame();
    expect(`.o_cp_searchview`).toHaveCount(1);
    expect(getFacetTexts()[0].replace("\n", "")).toBe("M2mFirst record or Second record");
});

test("globalContext keys in name_search", async () => {
    onRpc("name_search", ({ kwargs }) => {
        expect.step("name_search");
        expect(kwargs.context.specialKey).toBe("ABCD");
    });

    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="company"/>
            </search>
        `,
        context: { specialKey: "ABCD" },
    });
    await editSearch("F");
    await keyDown("ArrowRight");
    await animationFrame();
    expect.verifySteps(["name_search"]);
});

test("search a property", async () => {
    onRpc("web_search_read", ({ kwargs }) => {
        if (kwargs.specification.display_name && kwargs.specification.child_properties) {
            const definition1 = [
                {
                    type: "many2one",
                    string: "My Partner",
                    name: "my_partner",
                    comodel: "partner",
                },
                {
                    type: "many2many",
                    string: "My Partners",
                    name: "my_partners",
                    comodel: "partner",
                },
                {
                    type: "selection",
                    string: "My Selection",
                    name: "my_selection",
                    selection: [
                        ["a", "A"],
                        ["b", "B"],
                        ["c", "C"],
                        ["aa", "AA"],
                    ],
                },
                {
                    type: "tags",
                    string: "My Tags",
                    name: "my_tags",
                    tags: [
                        ["a", "A", 1],
                        ["b", "B", 5],
                        ["c", "C", 3],
                        ["aa", "AA", 2],
                    ],
                },
            ];

            const definition2 = [
                {
                    type: "char",
                    string: "My Text",
                    name: "my_text",
                },
            ];

            return {
                records: [
                    { id: 1, display_name: "Bar 1", child_properties: definition1 },
                    { id: 2, display_name: "Bar 2", child_properties: definition2 },
                ],
            };
        }
    });
    onRpc("name_search", ({ kwargs }) => {
        if (kwargs.name === "Bo") {
            return [
                [5, "Bob"],
                [6, "Bobby"],
            ];
        } else if (kwargs.name === "Ali") {
            return [
                [9, "Alice"],
                [10, "Alicia"],
            ];
        }
    });

    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="properties"/>
            </search>
        `,
    });

    // expand the properties field
    await editSearch("a");
    await contains(".o_expand").click();

    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(9);
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Properties",
        "My Partner (Bar 1)",
        "My Partners (Bar 1)",
        "My Selection (Bar 1) for: A",
        "My Selection (Bar 1) for: AA",
        "My Tags (Bar 1) for: A",
        "My Tags (Bar 1) for: AA",
        "My Text (Bar 2) for: a",
        "Custom Filter...",
    ]);

    // click again on the expand icon to hide the properties
    await contains(".o_expand").click();
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(2);
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Properties",
        "Custom Filter...",
    ]);

    // search for a partner, and expand the many2many property
    await contains(`.o_searchview_input`).clear();
    await editSearch("Bo");
    await contains(".o_expand").click();
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-child(3) .o_expand").click();
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(7);
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Properties",
        "My Partner (Bar 1)",
        "My Partners (Bar 1)",
        "Bob",
        "Bobby",
        "My Text (Bar 2) for: Bo",
        "Custom Filter...",
    ]);

    // fold all the properties (included the search result)
    await contains(".o_expand").click();
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(2);
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Properties",
        "Custom Filter...",
    ]);

    // unfold all the properties but fold the search result
    await contains(".o_expand").click();
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-child(3) .o_expand").click();
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(5);
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Properties",
        "My Partner (Bar 1)",
        "My Partners (Bar 1)",
        "My Text (Bar 2) for: Bo",
        "Custom Filter...",
    ]);

    // select Bobby
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-child(3) .o_expand").click();
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-child(5)").click();
    expect(searchBar.env.searchModel.domain).toEqual([
        "&",
        ["bar", "=", 1],
        ["properties.my_partners", "in", 6],
    ]);

    // expand the selection properties
    await contains(".o_cp_searchview").click();
    await editSearch("a");
    await contains(".o_expand").click();
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(9);
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Properties",
        "My Partner (Bar 1)",
        "My Partners (Bar 1)",
        "My Selection (Bar 1) for: A",
        "My Selection (Bar 1) for: AA",
        "My Tags (Bar 1) for: A",
        "My Tags (Bar 1) for: AA",
        "My Text (Bar 2) for: a",
        "Custom Filter...",
    ]);

    // select the selection option "AA"
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-child(5)").click();
    expect(searchBar.env.searchModel.domain).toEqual([
        "&",
        "&",
        ["bar", "=", 1],
        ["properties.my_partners", "in", 6],
        "&",
        ["bar", "=", 1],
        ["properties.my_selection", "=", "aa"],
    ]);

    // select the selection option "A"
    await contains(".o_cp_searchview").click();
    await editSearch("a");
    await contains(".o_expand").click();
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-child(4)").click();
    expect(searchBar.env.searchModel.domain).toEqual([
        "&",
        "&",
        ["bar", "=", 1],
        ["properties.my_partners", "in", 6],
        "|",
        "&",
        ["bar", "=", 1],
        ["properties.my_selection", "=", "aa"],
        "&",
        ["bar", "=", 1],
        ["properties.my_selection", "=", "a"],
    ]);

    // reset the search
    await contains(".o_facet_remove").click();
    await contains(".o_facet_remove").click();

    // search a many2one value
    await contains(".o_cp_searchview").click();
    await editSearch("Ali");
    await contains(".o_expand").click();
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-child(2) .o_expand").click();
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(7);
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Properties",
        "My Partner (Bar 1)",
        "Alice",
        "Alicia",
        "My Partners (Bar 1)",
        "My Text (Bar 2) for: Ali",
        "Custom Filter...",
    ]);
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-child(4)").click();
    expect(searchBar.env.searchModel.domain).toEqual([
        "&",
        ["bar", "=", 1],
        ["properties.my_partner", "=", 10],
    ]);

    // search a tag value
    await contains(".o_cp_searchview").click();
    await editSearch("A");
    await contains(".o_expand").click();
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(9);
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Properties",
        "My Partner (Bar 1)",
        "My Partners (Bar 1)",
        "My Selection (Bar 1) for: A",
        "My Selection (Bar 1) for: AA",
        "My Tags (Bar 1) for: A",
        "My Tags (Bar 1) for: AA",
        "My Text (Bar 2) for: A",
        "Custom Filter...",
    ]);

    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-child(7)").click();
    expect(searchBar.env.searchModel.domain).toEqual([
        "&",
        "&",
        ["bar", "=", 1],
        ["properties.my_partner", "=", 10],
        "&",
        ["bar", "=", 1],
        ["properties.my_tags", "in", "aa"],
    ]);
    // add the tag "B"
    await contains(".o_cp_searchview").click();
    await editSearch("B");
    await contains(".o_expand").click();
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(7);
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Properties",
        "My Partner (Bar 1)",
        "My Partners (Bar 1)",
        "My Selection (Bar 1) for: B",
        "My Tags (Bar 1) for: B",
        "My Text (Bar 2) for: B",
        "Custom Filter...",
    ]);
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-child(5)").click();
    expect(searchBar.env.searchModel.domain).toEqual([
        "&",
        "&",
        ["bar", "=", 1],
        ["properties.my_partner", "=", 10],
        "|",
        "&",
        ["bar", "=", 1],
        ["properties.my_tags", "in", "aa"],
        "&",
        ["bar", "=", 1],
        ["properties.my_tags", "in", "b"],
    ]);

    // try to click on the many2one properties without unfolding
    // it should not add the domain, but unfold the item
    await editSearch("Bobby");
    await contains(".o_expand").click();
    await contains(".o_searchview_autocomplete .o-dropdown-item:nth-child(2)").click();
    expect(searchBar.env.searchModel.domain).toEqual([
        "&",
        "&",
        ["bar", "=", 1],
        ["properties.my_partner", "=", 10],
        "|",
        "&",
        ["bar", "=", 1],
        ["properties.my_tags", "in", "aa"],
        "&",
        ["bar", "=", 1],
        ["properties.my_tags", "in", "b"],
    ]);
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(6);
    expect(queryAllTexts`.o_searchview_autocomplete .o-dropdown-item`).toEqual([
        "Search Properties",
        "My Partner (Bar 1)",
        "(no result)",
        "My Partners (Bar 1)",
        "My Text (Bar 2) for: Bobby",
        "Custom Filter...",
    ]);

    // test the navigation with keyboard
    await contains(`.o_searchview_input`).clear();
    await animationFrame();
    await runAllTimers();

    await editSearch("Bo");
    await animationFrame();
    expect(`.o-dropdown-item.focus`).toHaveText("Search Properties");
    // unfold the properties field
    await keyDown("ArrowRight");
    await animationFrame();
    await runAllTimers();
    expect(`.o-dropdown-item.focus`).toHaveText("Search Properties");
    expect(".o-dropdown-item.focus:only .fa-caret-down").toHaveCount(1);
    // move on the many2one property
    await keyDown("ArrowRight", { repeat: false });
    await animationFrame();
    await runAllTimers();
    expect(`.o-dropdown-item.focus`).toHaveText("My Partner (Bar 1)");
    expect(".o-dropdown-item.focus:only .fa-caret-right").toHaveCount(1);
    // move on the many2many property
    await keyDown("ArrowDown");
    await animationFrame();
    expect(`.o-dropdown-item.focus`).toHaveText("My Partners (Bar 1)");
    expect(".o-dropdown-item.focus:only .fa-caret-right").toHaveCount(1);
    // move on the many2one property again
    await keyDown("ArrowUp");
    await animationFrame();
    expect(`.o-dropdown-item.focus`).toHaveText("My Partner (Bar 1)");
    expect(".o-dropdown-item.focus:only .fa-caret-right").toHaveCount(1);
    // unfold the many2one
    await keyDown("ArrowRight");
    await animationFrame();
    await runAllTimers();
    expect(`.o-dropdown-item.focus`).toHaveText("My Partner (Bar 1)");
    expect(".o-dropdown-item.focus:only .fa-caret-down").toHaveCount(1);
    // select the first many2one
    await keyDown("ArrowRight", { repeat: false });
    await animationFrame();
    await runAllTimers();
    expect(`.o-dropdown-item.focus`).toHaveText("Bob");
    // go up on the parent
    await keyDown("ArrowLeft");
    await animationFrame();
    await runAllTimers();
    expect(`.o-dropdown-item.focus`).toHaveText("My Partner (Bar 1)");
    expect(".o-dropdown-item.focus:only .fa-caret-down").toHaveCount(1);
    // fold the parent
    await keyDown("ArrowLeft");
    await animationFrame();
    await runAllTimers();
    expect(`.o-dropdown-item.focus`).toHaveText("My Partner (Bar 1)");
    expect(".o-dropdown-item.focus:only .fa-caret-right").toHaveCount(1);
    // go up on the properties field
    await keyDown("ArrowLeft");
    await animationFrame();
    await runAllTimers();
    expect(`.o-dropdown-item.focus`).toHaveText("Search Properties");
    expect(".o-dropdown-item.focus:only .fa-caret-down").toHaveCount(1);
    // fold the properties field
    await keyDown("ArrowLeft");
    await animationFrame();
    await runAllTimers();
    expect(`.o-dropdown-item.focus`).toHaveText("Search Properties");
    expect(".o-dropdown-item.focus:only .fa-caret-right").toHaveCount(1);
});

test("search a property: definition record id in the context", async () => {
    onRpc("web_search_read", ({ kwargs }) => {
        if (kwargs.specification.display_name && kwargs.specification.child_properties) {
            expect.step("web_search_read");
            expect(kwargs.domain).toEqual(["&", ["child_properties", "!=", false], ["id", "=", 2]]);

            const definition2 = [
                {
                    type: "char",
                    string: "My Text",
                    name: "my_text",
                },
            ];

            return {
                records: [{ id: 2, display_name: "Bar 2", child_properties: definition2 }],
            };
        }
    });

    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="properties"/>
            </search>
        `,
        context: { active_id: 2 },
    });

    await contains(".o_cp_searchview").click();
    await editSearch("a");
    await contains(".o_expand").click();
    expect.verifySteps(["web_search_read"]);
    expect(`.o_searchview_autocomplete .o-dropdown-item`).toHaveCount(3);
    expect(queryAll`.o_searchview_autocomplete .o-dropdown-item`[1]).toHaveText(
        "My Text (Bar 2) for: a"
    );
});

test("edit a filter", async () => {
    onRpc("/web/domain/validate", () => true);
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: ["groupBy"], // we need it to have facet (see facets getter in search_model)
        searchViewId: false,
        searchViewArch: `
            <search>
                <filter name="filter" string="Filter" domain="[('birthday', '>=', context_today())]"/>
                <filter name="bool" string="Bool" domain="[]" context="{'group_by': 'bool'}"/>
            </search>
        `,
        context: {
            search_default_filter: true,
            search_default_bool: true,
        },
    });
    expect(getFacetTexts()).toEqual(["Filter", "Bool"]);
    expect(`.o_searchview_facet .o_searchview_facet_label`).toHaveCount(2);
    expect(`.o_searchview_facet.o_facet_with_domain .o_searchview_facet_label`).toHaveCount(1);
    expect(`.modal`).toHaveCount(0);

    await contains(".o_facet_with_domain .o_searchview_facet_label").click();
    expect(`.modal`).toHaveCount(1);
    expect(`.modal header`).toHaveText("Custom Filter");
    expect(`.modal .o_domain_selector`).toHaveCount(1);
    expect(SELECTORS.condition).toHaveCount(1);
    expect(queryAllTexts`.modal footer button`).toEqual(["Search", "Discard"]);
    expect(getCurrentPath()).toBe("Birthday");
    expect(getCurrentOperator()).toBe(label(">="));
    expect(getCurrentValue()).toBe("context_today()");
    expect(`.modal footer button`).toBeEnabled();

    await clickOnButtonDeleteNode();
    expect(SELECTORS.condition).toHaveCount(0);
    expect(`.modal footer button:first`).not.toBeEnabled();

    await contains(`.modal ${SELECTORS.addNewRule}`).click();
    expect(SELECTORS.condition).toHaveCount(1);
    expect(getCurrentPath()).toBe("Id");
    expect(getCurrentOperator()).toBe(label("="));
    expect(getCurrentValue()).toBe("1");

    await contains(".modal footer button").click();
    expect(`.modal`).toHaveCount(0);
    expect(getFacetTexts()).toEqual(["Id = 1", "Bool"]);
});

test("edit a filter with context: context is kept after edition", async () => {
    onRpc("/web/domain/validate", () => true);
    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchViewId: false,
        searchViewArch: `
            <search>
                <filter name="filter" string="Filter"  context="{'specialKey': 'abc'}" domain="[('foo', '=', 'abc')]"/>
            </search>
        `,
        context: {
            search_default_filter: true,
        },
    });
    expect(getFacetTexts()).toEqual(["Filter"]);
    expect(searchBar.env.searchModel.context.specialKey).toBe("abc");

    await contains(".o_facet_with_domain .o_searchview_facet_label").click();
    await contains(`.modal ${SELECTORS.addNewRule}`).click();
    await contains(".modal footer button").click();
    expect(getFacetTexts()).toEqual([`Foo = abc`, `Foo = abc`]);
    expect(searchBar.env.searchModel.context.specialKey).toBe("abc");
});

test("edit a favorite", async () => {
    const irFilters = [
        {
            context: "{ 'some_key': 'some_value', 'group_by': ['bool'] }",
            domain: "[('foo', 'ilike', 'abc')]",
            id: 1,
            is_default: true,
            name: "My favorite",
            sort: "[]",
            user_ids: [2],
        },
    ];

    onRpc("/web/domain/validate", () => true);
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: ["groupBy"], // we need it to have facet (see facets getter in search_model)
        searchViewId: false,
        searchViewArch: `
            <search>
                <filter name="company" string="Company" domain="[]" context="{'group_by': 'company'}"/>
            </search>
        `,
        irFilters,
    });
    expect(getFacetTexts()).toEqual(["My favorite"]);
    expect(`.o_searchview_facet.o_facet_with_domain .o_searchview_facet_label`).toHaveCount(1);

    await toggleSearchBarMenu();
    await toggleMenuItem("Company");
    expect(getFacetTexts()).toEqual(["My favorite", "Company"]);
    expect(`.o_searchview_facet .o_searchview_facet_label`).toHaveCount(2);
    expect(`.o_searchview_facet.o_facet_with_domain .o_searchview_facet_label`).toHaveCount(1);

    await contains(".o_facet_with_domain .o_searchview_facet_label").click();
    expect(`.modal`).toHaveCount(1);
    expect(getCurrentPath()).toBe("Foo");
    expect(getCurrentOperator()).toBe("contains");
    expect(getCurrentValue()).toBe("abc");

    await editValue("def");
    expect(getCurrentPath()).toBe("Foo");
    expect(getCurrentOperator()).toBe("contains");
    expect(getCurrentValue()).toBe("def");

    await contains(".modal footer button").click();
    expect(`.modal`).toHaveCount(0);
    expect(getFacetTexts()).toEqual([`Foo ${label("ilike")} def`, "Bool\n>\nCompany"]);
});

test("edit a field", async () => {
    onRpc("/web/domain/validate", () => true);
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="foo"/>
            </search>
        `,
        context: {
            search_default_foo: "abc",
        },
    });
    expect(getFacetTexts()).toEqual(["Foo\nabc"]);
    expect(`.o_searchview_facet.o_facet_with_domain .o_searchview_facet_label`).toHaveCount(1);

    await editSearch("def");
    await keyDown("Enter"); // select
    await animationFrame();
    expect(getFacetTexts()).toEqual(["Foo\nabc\nor\ndef"]);

    await contains(".o_facet_with_domain .o_searchview_facet_label").click();
    expect(SELECTORS.condition).toHaveCount(2);

    expect(getCurrentPath(0)).toBe("Foo");
    expect(getCurrentOperator(0)).toBe("contains");
    expect(getCurrentValue(0)).toBe("abc");
    expect(getCurrentPath(1)).toBe("Foo");
    expect(getCurrentOperator(1)).toBe("contains");
    expect(getCurrentValue(1)).toBe("def");

    await contains(".modal footer button").click();
    expect(getFacetTexts()).toEqual([`Foo\nabc\nor\ndef`]);
});

test("no rpc for getting display_name for facets if known", async () => {
    onRpc("/web/domain/validate", () => true);
    onRpc("name_search", ({ kwargs }) => {
        expect.step(kwargs.domain);
    });
    onRpc(({ method }) => expect.step(method));

    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchViewId: false,
        searchViewArch: `
            <search>
                <filter name="filter" string="Filter" domain="[('bar', 'in', [])]"/>
            </search>
        `,
        context: {
            search_default_filter: true,
        },
    });
    expect(getFacetTexts()).toEqual(["Filter"]);
    expect.verifySteps(["get_views"]);

    await contains(".o_facet_with_domain .o_searchview_facet_label").click();
    expect.verifySteps(["fields_get"]);

    await contains(".o-autocomplete--input").click();
    expect.verifySteps(["name_search", ["!", ["id", "in", []]]]);

    await contains(".o-autocomplete--dropdown-menu .o-autocomplete--dropdown-item").click();
    await contains(".modal footer button").click();
    expect(getFacetTexts()).toEqual(["Bar = First record"]);
});

test.tags("desktop");
test("clicking on search input trigger the search menu", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
    });
    await contains(`.o_searchview_input`).click();
    expect(`.o_search_bar_menu`).toHaveCount(1);
});

test("clicking on the searchview icon trigger the search", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchViewId: false,
    });
    await editSearch("a");
    await contains(`.o_searchview button`).click();
    expect(`.o_searchview_input_container .o_facet_values`).toHaveText("a");
});

test("facets display with any / not any operator", async function () {
    onRpc(({ method }) => expect.step(method));
    onRpc("/web/domain/validate", () => {
        expect.step("/web/domain/validate");
        return true;
    });

    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchViewId: false,
        searchViewArch: `
            <search>
                <filter name="filter" string="Filter" domain="[('company', 'any', [('bar', 'any', [('company', 'in', ['JD7', 'KDB'])])])]"/>
            </search>
        `,
        context: {
            search_default_filter: true,
        },
    });
    expect(getFacetTexts()).toEqual(["Filter"]);
    expect.verifySteps([`get_views`]);

    await contains(".o_facet_with_domain .o_searchview_facet_label").click();
    expect.verifySteps([`fields_get`]);

    await addNewRule();

    await contains(".modal footer button").click();
    expect(getFacetTexts()).toEqual([
        "Company : ( Bar : ( Company = JD7 or KDB and Company = JD7 or KDB ) )",
    ]);
    expect.verifySteps([`/web/domain/validate`]);
});

test("facets display with any / not any operator (with a complex path)", async function () {
    onRpc(({ method }) => expect.step(method));
    onRpc("/web/domain/validate", () => {
        expect.step("/web/domain/validate");
        return true;
    });
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchViewId: false,
        searchViewArch: `
            <search>
                <filter name="filter" string="Filter" domain="['|', ('company.company', 'any', [('id', '=', 1)]), ('Bar', '=', false)]"/>
            </search>
        `,
        context: {
            search_default_filter: true,
        },
    });
    expect(getFacetTexts()).toEqual(["Filter"]);
    expect.verifySteps([`get_views`]);

    await contains(".o_facet_with_domain .o_searchview_facet_label").click();
    expect.verifySteps([`fields_get`]);

    await addNewRule();

    await contains(".modal footer button").click();
    expect(getFacetTexts()).toEqual([
        `Company ➔ Company : ( Id = 1 and Id = 1 ) or Bar ${label("not set")}`,
    ]);
    expect.verifySteps([`/web/domain/validate`]);
});

test("facets display with any / not any operator (with a or)", async function () {
    onRpc(({ method }) => expect.step(method));
    onRpc("/web/domain/validate", () => {
        expect.step("/web/domain/validate");
        return true;
    });
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchViewId: false,
        searchViewArch: `
            <search>
                <filter name="filter" string="Filter" domain="['|', ('company', 'any', [('id', '=', 1)]), ('bar', '=', false)]"/>
            </search>
        `,
        context: {
            search_default_filter: true,
        },
    });
    expect(getFacetTexts()).toEqual(["Filter"]);
    expect.verifySteps([`get_views`]);

    await contains(".o_facet_with_domain .o_searchview_facet_label").click();
    expect.verifySteps([`fields_get`]);

    await addNewRule();

    await contains(".modal footer button").click();
    expect(getFacetTexts()).toEqual([`Company : ( Id = 1 and Id = 1 ) or Bar ${label("not set")}`]);
    expect.verifySteps([`/web/domain/validate`]);
});

test("facets display with any / not any operator (check brackets)", async function () {
    onRpc(({ method }) => expect.step(method));
    onRpc("/web/domain/validate", () => {
        expect.step("/web/domain/validate");
        return true;
    });
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchViewId: false,
        searchViewArch: `
            <search>
                <filter isDebugMode="true" name="filter" string="Filter" domain="['|', ('company', 'any', [('bar', 'any', [('bool', '=', False)]), ('bar', 'any', [('bool', '=', True)])]), ('bar', '=', false)]"/>
            </search>
        `,
        context: {
            search_default_filter: true,
        },
    });
    expect(getFacetTexts()).toEqual(["Filter"]);
    expect.verifySteps([`get_views`]);

    await contains(".o_facet_with_domain .o_searchview_facet_label").click();
    expect.verifySteps([`fields_get`]);

    await addNewRule();

    await contains(".modal footer button").click();
    expect(getFacetTexts()).toEqual([
        `Company : ( Bar : ( Bool ${label("not set")} and Bool ${label("not set")} ) and Bar : ( Bool ${label("set")} ) ) or Bar ${label("not set")}`,
    ]);
    expect.verifySteps([`/web/domain/validate`]);
});

test("select autocompleted many2one with allowed_company_ids domain (cids: 1-5)", async () => {
    cookie.set("cids", "1-5");
    // allowed_company_ids is initially set by the company_service
    serverState.companies = [
        ...serverState.companies,
        // company_service only includes existing companies from cids
        {
            id: 5,
            name: "Hierophant",
        },
    ];

    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="bar" domain="[('company', 'in', allowed_company_ids)]"/>
            </search>
        `,
    });

    await editSearch("rec");
    await contains(`.o_expand`).click();
    expect(queryAllTexts(`.o_searchview_autocomplete .o-dropdown-item`)).toEqual([
        "Search Bar for: rec",
        "Second record",
        "Third record",
        "Custom Filter...",
    ]);
});

test("select autocompleted many2one with allowed_company_ids domain (cids: 1)", async () => {
    cookie.set("cids", "1");
    // allowed_company_ids is initially set by the company_service
    serverState.companies = [
        ...serverState.companies,
        // company_service only includes existing companies from cids
        {
            id: 5,
            name: "Hierophant",
        },
    ];

    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="bar" domain="[('company', 'in', allowed_company_ids)]"/>
            </search>
        `,
    });

    await click(".o_searchview input");
    await clear();
    await animationFrame();

    await editSearch("rec");
    await contains(`.o_expand`).click();
    await runAllTimers();
    expect(queryAllTexts(`.o_searchview_autocomplete .o-dropdown-item`)).toEqual([
        "Search Bar for: rec",
        "Second record",
        "Custom Filter...",
    ]);
});

test("throw error when domain can not be parsed", async () => {
    expect.errors(1);
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="bar" domain="[('company', 'in', wrong)]"/>
            </search>
        `,
    });

    await editSearch("rec");
    await contains(`.o_expand`).click();
    expect.verifyErrors(["Error: Name 'wrong' is not defined"]);
});

test("dropdown menu last element is 'Custom Filter...'", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="foo"/>
            </search>
        `,
    });
    await editSearch("a");
    await animationFrame();
    expect(".o_searchview_autocomplete .o-dropdown-item:last").toHaveText("Custom Filter...");
});

test("order by count resets when there is no group left", async () => {
    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: ["groupBy", "filter"],
        searchViewId: false,
        searchViewArch: `
            <search>
                <filter string="Foo" name="foo" domain="[('foo', '=', 'qsdf')]"/>
            </search>
        `,
    });
    searchBar.env.searchModel.canOrderByCount = true;
    await toggleSearchBarMenu();
    await selectGroup("bool");
    await selectGroup("bar");
    await toggleMenuItem("Foo");
    expect(".fa-sort").toHaveCount(1);
    await contains(".fa-sort", { visible: false }).click();
    expect(".fa-sort-numeric-desc").toHaveCount(1);
    await contains(".fa-sort-numeric-desc").click();
    expect(".fa-sort-numeric-asc").toHaveCount(1);

    await toggleSearchBarMenu();
    await toggleMenuItem("Foo");
    expect(".fa-sort-numeric-asc").toHaveCount(1);

    await toggleMenuItem("Foo");
    await toggleMenuItem("Bool");
    expect(".fa-sort-numeric-asc").toHaveCount(1);
    await toggleMenuItem("Bar");
    expect(".fa-sort-numeric-asc").toHaveCount(0);

    await toggleMenuItem("Bar");
    expect(".fa-sort-numeric-asc").toHaveCount(0);
    expect(".fa-sort").toHaveCount(1);
    await contains(".fa-sort", { visible: false }).click();
    await contains(".fa-sort-numeric-desc").click();
    expect(".fa-sort-numeric-asc").toHaveCount(1);
    await toggleSearchBarMenu();
    await toggleMenuItem("Bool");
    expect(".fa-sort-numeric-asc").toHaveCount(1);

    await contains(".o_facet_remove").click();
    expect(".fa-sort-numeric-asc").toHaveCount(1);
    await contains(".o_facet_remove").click();
    expect(".o_searchview_facet").toHaveCount(0);

    await toggleSearchBarMenu();
    await toggleMenuItem("Bar");
    expect(".fa-sort-numeric-asc").toHaveCount(0);
    expect(".fa-sort").toHaveCount(1);
});

test("subitems have a load more item if there is more records available", async () => {
    for (let i = 0; i < 20; i++) {
        Partner._records.push({
            id: 100 + i,
            name: `Home Depot ${i}`,
        });
    }
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="company"/>
            </search>
        `,
    });
    await editSearch("Home");
    await contains(".o_expand").click();
    await expect(".o_searchview_autocomplete .o-dropdown-item.o_indent").toHaveCount(8 + 1);
    await expect(".o_searchview_autocomplete .o-dropdown-item.o_indent:last").toHaveText(
        "Load more"
    );
    await contains(".o_searchview_autocomplete .o-dropdown-item.o_indent:last").click();
    await expect(".o_searchview_autocomplete .o-dropdown-item.o_indent").toHaveCount(8 + 8 + 1);
    await expect(".o_searchview_autocomplete .o-dropdown-item.o_indent:last").toHaveText(
        "Load more"
    );
});

test("subitems do not have a load more item if there is no more records available", async () => {
    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
        searchViewArch: `
            <search>
                <field name="company"/>
            </search>
        `,
    });
    await editSearch("home");
    await contains(".o_expand").click();
    await expect(".o_searchview_autocomplete .o-dropdown-item.o_indent").toHaveCount(1);
    await expect(".o_searchview_autocomplete .o-dropdown-item.o_indent").toHaveText("(no result)");
});

test("single name_search call and no flicker when holding ArrowRight", async function () {
    onRpc(({ method }) => {
        if (method === "name_search") {
            expect.step(method);
        }
    });

    await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });

    await editSearch("a");
    await press("arrowdown");
    await press("arrowleft");
    await animationFrame();

    for (let i = 0; i < 3; i++) {
        await press("arrowright", { repeat: i > 0 });
        await animationFrame();
        expect(".o_menu_item.o_indent").toHaveCount(0);
        expect("input.o_searchview_input").toBeFocused();
    }
    await press("arrowright");
    expect.verifySteps(["name_search"]);
});

test.tags("desktop");
test("no crash when search component is destroyed with input", async () => {
    const def = new Deferred();
    onRpc("web_read", () => def);
    defineWebModels();
    await mountWebClient();
    await getService("action").doAction(1);
    expect(".o_list_view").toHaveCount(1);
    await contains(".o_data_cell:eq(0)").click();
    expect(".o_list_view").toHaveCount(1);
    await editSearch("Jethalal");
    def.resolve();
    await animationFrame();
    await runAllTimers();
    expect(".o_form_view").toHaveCount(1);
});

test("search on full query without waiting for display synchronisation", async () => {
    /* Typically a barcode scan where the dropdown display doesn't have the time to update */
    const searchBar = await mountWithSearch(SearchBar, {
        resModel: "partner",
        searchMenuTypes: [],
        searchViewId: false,
    });

    await editSearch("01234");
    expect(".o-dropdown-item:first").toHaveText("Search Foo for: 01234");
    await press("5");
    expect(".o-dropdown-item:first").toHaveText("Search Foo for: 01234");
    await press("6");
    expect(".o-dropdown-item:first").toHaveText("Search Foo for: 01234");
    await keyDown("Enter");
    expect(searchBar.env.searchModel.domain).toEqual([["foo", "ilike", "0123456"]]);
});
